using Unity.Burst;                      // Burst 编译器，用于将 Job 代码编译为接近原生性能的机器码
using Unity.Collections;                // 提供高性能数据结构，如 NativeArray、ComputeBuffer
using Unity.Jobs;                       // Unity Job System，用于多线程任务调度
using Unity.Mathematics;                // 提供 math 函数，如 floor, float 等
using UnityEngine;                      // Unity 基础功能

// 引入静态数学函数，简化书写
using static Unity.Mathematics.math;

// =============================================
// 脚本名称：HashVisualization
// 用途：通过计算二维网格上每个点的哈希值，并利用 GPU 实例化渲染，
//       可视化这些哈希值（比如作为颜色、位置偏移、动画种子等）。
//       每个网格点对应一个渲染实例，哈希值由 SmallXXHash 计算得到。
// =============================================
public class HashVisualization : MonoBehaviour
{
	// --------------------------------------------------------------------------
	// 【Job 定义】并行计算每个网格点的哈希值
	// 使用 IJobFor，对每个索引 i （代表一个网格点）进行并行计算
	// --------------------------------------------------------------------------
	[BurstCompile(FloatPrecision.Standard, FloatMode.Fast, CompileSynchronously = true)]
	struct HashJob : IJobFor
	{
		// 【只写】输出：每个网格点对应的哈希值，将传递给 GPU
		[WriteOnly]
		public NativeArray<uint> hashes;

		// 网格的分辨率（比如 16x16 = 256 个点）
		public int resolution;

		// 每个网格单元在世界空间或逻辑空间中的“倒数分辨率”，用于坐标计算
		public float invResolution;

		// 使用的哈希函数工具类（SmallXXHash），已通过特定种子初始化
		public SmallXXHash hash;

		// 每个索引 i 对应一个网格点，计算其在网格中的 u（x）和 v（y）坐标
		public void Execute(int i)
		{
			// 计算当前点在网格中的 “行号” v（y 方向）
			// i 是一维索引，我们将其映射回二维网格坐标
			int v = (int)floor(invResolution * i + 0.00001f);

			// 计算当前点在网格中的 “列号” u（x 方向）
			// 公式：u = i - v * resolution - resolution / 2;
			// 目的是让网格中心对称（偏移 resolution/2 是为了让坐标从 -N/2 到 N/2）
			int u = i - resolution * v - resolution / 2;

			// 同样将 v 也中心化（从 -N/2 到 N/2）
			v -= resolution / 2;

			// 调用 hash.Eat(u).Eat(v)：
			//   - Eat() 是一个链式哈希函数，先“吃掉” u，再“吃掉” v，生成一个唯一哈希
			//   - 最终将计算出的哈希值存入 hashes[i]
			hashes[i] = hash.Eat(u).Eat(v);
		}
	}

	// --------------------------------------------------------------------------
	// 【Shader 相关常量】：属性 ID，用于高效设置 MaterialPropertyBlock 参数
	// --------------------------------------------------------------------------
	static int
		hashesId = Shader.PropertyToID("_Hashes"),       // 哈希值缓冲区的属性 ID
		configId = Shader.PropertyToID("_Config");       // 配置参数（分辨率、倒数分辨率、垂直偏移等）

	// --------------------------------------------------------------------------
	// 【序列化字段】：可在 Unity Inspector 中调节的参数
	// --------------------------------------------------------------------------
	[SerializeField]
	Mesh instanceMesh;                  // 渲染用的网格（通常是简单的立方体/四边形）

	[SerializeField]
	Material material;                  // 使用的材质，需支持接收 _Hashes 缓冲区和配置参数

	[SerializeField, Range(1, 512)]     // 网格分辨率，控制一共生成多少个实例（比如 16x16 = 256 个）
	int resolution = 16;

	[SerializeField, Range(-2f, 2f)]    // 垂直方向上的偏移量，可能用于控制渲染效果
	float verticalOffset = 1f;

	[SerializeField]
	int seed;                           // 哈希函数的随机种子，影响生成的哈希值分布

	// --------------------------------------------------------------------------
	// 【运行时数据】
	// --------------------------------------------------------------------------
	NativeArray<uint> hashes;           // 存储每个网格点的哈希值（CPU 端）
	ComputeBuffer hashesBuffer;         // 将哈希值传递到 GPU 的 ComputeBuffer
	MaterialPropertyBlock propertyBlock; // 用于高效设置材质参数，避免频繁创建 Material

	// --------------------------------------------------------------------------
	// 【生命周期方法】OnEnable：初始化计算与数据传递
	// --------------------------------------------------------------------------
	void OnEnable()
	{
		int length = resolution * resolution; // 总点数 = 分辨率 ^ 2

		// 创建 NativeArray，用于存储每个点的哈希值（uint 类型）
		hashes = new NativeArray<uint>(length, Allocator.Persistent);

		// 创建 ComputeBuffer，用于将哈希值传递到 GPU
		// 每个 uint 占 4 字节
		hashesBuffer = new ComputeBuffer(length, 4);

		// 创建一个 HashJob，设置参数并调度并行计算
		new HashJob
		{
			hashes = hashes,                          // 输出哈希数组
			resolution = resolution,                  // 网格分辨率
			invResolution = 1f / resolution,          // 每个格子的倒数分辨率，用于坐标计算
			hash = SmallXXHash.Seed(seed)             // 初始化哈希函数，使用指定种子
		}
		// 调度 Job：并行计算所有网格点（i 从 0 到 length-1）
		// 共有hashes.Length个元素，每个元素需要计算一次哈希值
		// 共有 length / resolution 个线程，每个线程处理 resolution 个元素，提高缓存命中率
		// 这个 Job 不依赖任何其他 Job，可以立即开始执行​
		.ScheduleParallel(hashes.Length, resolution, default)
		.Complete(); // 等待所有并行任务完成

		// 将计算好的哈希值从 NativeArray 拷贝到 ComputeBuffer（GPU）
		hashesBuffer.SetData(hashes);

		// 初始化 MaterialPropertyBlock（用于高效设置 Shader 参数）
		propertyBlock ??= new MaterialPropertyBlock();

		// 设置 Shader 中的哈希缓冲区
		propertyBlock.SetBuffer(hashesId, hashesBuffer);

		// 设置一个 Vector4 配置参数，包括：
		// - 分辨率
		// - 倒数分辨率
		// - 垂直偏移量（可能用于 Shader 中的视觉调整）
		propertyBlock.SetVector(configId, new Vector4(
			resolution, 1f / resolution, verticalOffset / resolution
		));
	}

	// --------------------------------------------------------------------------
	// 【生命周期方法】OnDisable：释放资源，防止内存泄漏
	// --------------------------------------------------------------------------
	void OnDisable()
	{
		// 释放 NativeArray
		hashes.Dispose();

		// 释放 ComputeBuffer
		if (hashesBuffer != null)
		{
			hashesBuffer.Release();
			hashesBuffer = null;
		}
	}

	// --------------------------------------------------------------------------
	// 【生命周期方法】OnValidate：当 Inspector 中参数被修改时重新初始化
	// --------------------------------------------------------------------------
	void OnValidate()
	{
		// 如果当前已经启用且在运行，则参数改变时重新初始化
		if (hashesBuffer != null && enabled)
		{
			OnDisable();
			OnEnable();
		}
	}

	// --------------------------------------------------------------------------
	// 【生命周期方法】Update：每一帧调用，执行实例化渲染
	// --------------------------------------------------------------------------
	void Update()
	{
		// 使用 Graphics.DrawMeshInstancedProcedural：
		// - 一次性绘制所有哈希点对应的实例（比如小方块）
		// - 使用 ComputeBuffer 传入的哈希值作为每个实例的“唯一标识”
		// - 无需逐个设置变换或颜色，由 Shader 根据 _Hashes 缓冲区动态处理
		Graphics.DrawMeshInstancedProcedural(
			instanceMesh,     // 渲染的网格
			0,                // 子网格索引
			material,         // 使用的材质
			new Bounds(Vector3.zero, Vector3.one), // 包围盒，用于视锥剔除
			hashes.Length,    // 实例数量
			propertyBlock     // 包含哈希缓冲区与配置参数的 MaterialPropertyBlock
		);
	}
}